import pandas as pd
import jieba
import gensim
import matplotlib.pyplot as plt
import re
from gensim.models import KeyedVectors
import numpy as np

print("-----------加载——-------------")
cn_model = KeyedVectors.load_word2vec_format("Chinese-Word-Vectors-master/sgns.zhihu.bigram"
                                             , binary=False)
print("-----------加载成功-------------")
embedding_dim = cn_model['山东大学'].shape[0]
print('词向量的长度:{}'.format(embedding_dim))

# 计算相似度
print(cn_model.similarity("小学生", "大学生"))
# 找出最相近的词
print(cn_model.most_similar(positive=['大学'], topn=10))
# 找出不同的词
test_words = '老师 会计师 程序员 律师 医生 老人'
test_words_result = cn_model.doesnt_match(test_words.split())
print('不同的词为:' + test_words_result)

import os

pos_txts = os.listdir('Chinese-Word-Vectors-master/ChnSentiCorp_htl_ba_2000/pos')
neg_txts = os.listdir('Chinese-Word-Vectors-master/ChnSentiCorp_htl_ba_2000/neg')
print('样本样例数目：' + str(len(pos_txts) + len(neg_txts)))

# 现在我们把所有的评价内容放到一个list中
train_texts_orig = []  # 存储所有评价，每个评价为一条string
# 前1000条为正面评价  后1000条为负面评价
for i in range(len(pos_txts)):
    with open('Chinese-Word-Vectors-master/ChnSentiCorp_htl_ba_2000/pos/' + pos_txts[i], 'r', errors='ignore') as f:
        text = f.read().strip()
        train_texts_orig.append(text)
        f.close()
for i in range(len(neg_txts)):
    with open('Chinese-Word-Vectors-master/ChnSentiCorp_htl_ba_2000/neg/' + neg_txts[i], 'r', errors='ignore') as f:
        text = f.read().strip()
        train_texts_orig.append(text)
        f.close()
print(len(train_texts_orig))

# 然后我们使用TensorFlow 的keras 来建模
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, GRU, Embedding, LSTM, Bidirectional
from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.optimizers import RMSprop
from tensorflow.python.keras.optimizers import Adam
from tensorflow.python.keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard, ReduceLROnPlateau

# 开始分词  和索引化
# 得到所有的索引
train_tokens = []
for text in train_texts_orig:
    # 去掉标点
    text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——！，。？、~@#￥%……&（）]+", "", text)
    # jieba 分词
    cut = jieba.cut(text)
    # jieba分词的输出结果为一个生成器
    # 把生成器转换为list
    cut_list = [i for i in cut]
    for i, word in enumerate(cut_list):
        try:
            # 将词转换为索引index
            cut_list[i] = cn_model.vocab[word].index
        except KeyError:
            # 如果词不在字典中，则存入为0
            cut_list[i] = 0
    train_tokens.append(cut_list)
# 获得所有tokens的长度
# 因为所有的索引的长度不一致 我们需要把这些一致化一下
num_tokens = [len(tokens) for tokens in train_tokens]
num_tokens = np.array(num_tokens)
# 平均token长度
print(np.mean(num_tokens))
# 最长的评价token的长度
print(np.max(num_tokens))
# 我们取tokens平均值+两个tokens的标准差
# 假设tokens长度的分布为正态分布，则max_tokens这个值就可以涵盖95%的置信区间了
max_tokens = np.mean(num_tokens) + 2 * np.std(num_tokens)
max_tokens = int(max_tokens)
print(max_tokens)

# 我们取max_tokens长度的时候 看一下有多少样本被涵盖
# 我们对长度不足的进行padding,对长度过长的进行修剪
print(np.sum(num_tokens < max_tokens) / len(num_tokens))


# 然后我们进行反索引化
# 把索引转换为可阅读的文本
def reverse_tokens(tokens):
    text = ''
    for i in tokens:
        if i != 0:
            text = text + cn_model.index2word[i]
        else:
            text = text + ''
    return text


reverse = reverse_tokens(train_tokens[0])
print(reverse)
'''
我们准备Embedding Matrix
现在我们来为模型准备embedding matrix（词向量矩阵），根据keras，我们需要准备一个维度为
(numwords,embeddingdim) 的矩阵，
num words代表我们使用的词汇的数量，
emdeddingdim 在我们现在使用的预训练模型向量模型中的是300，每一个词汇都使用一个长度为300的
向量表示

'''
num_words = 50000
# 初始化embedding_matrix
embedding_matrix = np.zeros((num_words, embedding_dim))
# embedding_matrix 为一个[num_words，embedding_dim] 的矩阵
# 维度50000*300
for i in range(num_words):
    embedding_matrix[i, :] = cn_model[cn_model.index2word[i]]
embedding_matrix = embedding_matrix.astype('float32')

# 检查index是否对应
# 输出300意义为长度300的embedding向量一一对应
print(np.sum(cn_model[cn_model.index2word[333]] == embedding_matrix[333]))

print(embedding_matrix.shape)

# padding填充  和 truncating 修剪
'''
我们把文本转换为tokens索引之后，每一串索引的长度并不相等，所以为了方便模型的训练 我们需要把所有的长度
标准化，上面我们选择了236这个可以覆盖95%的训练样本的长度,接下来我们进行padding 和 truncating，我们一般
采用'pre'的方法，这回在文本索引的前面填充0，因为根据一些研究资料中的实践，如果在后面填充0的话会造成不好的影响
'''
# 进行padding  和 truncating，输入的train_tokens 是 一个list
# 返回的train_pad 是一个numpy array
train_pad = pad_sequences(train_tokens, maxlen=max_tokens,
                          padding='pre', truncating='pre')
# 超出5万个词向量的词 用0代替
train_pad[train_pad >= num_words] = 0
# 可见padding之后前面的tokens全变为0，文本在最后
print(train_pad[33])

# 准备target向量
train_target = np.concatenate((np.ones(1000), np.zeros(1000)))
# 进行训练和测试的分割
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(train_pad,
                                                    train_target,
                                                    test_size=0.1,
                                                    random_state=12)
# 查看训练样本 确认
print(reverse_tokens(X_train[35]))
print('class: ', y_train[35])
# 开始搭建LSTM模型

model = Sequential()
model.add(Embedding(num_words, embedding_dim, weights=[embedding_matrix],
                    input_length=max_tokens, trainable=False))
model.add(Bidirectional(LSTM(units=32, return_sequences=True)))
model.add(LSTM(units=16, return_sequences=False))

model.add(Dense(1, activation='sigmoid'))
optimizer = Adam(lr=1e-3)
model.compile(loss='binary_crossentropy',
              optimizer=optimizer,
              metrics=['accuracy'])
print(model.summary())

# 建立一个权重的存储点
path_checkpoint = 'sentiment_checkpoint.keras'
checkpoint = ModelCheckpoint(filepath=path_checkpoint, monitor='val_loss',
                             verbose=1, save_weights_only=True,
                             save_best_only=True)
# 尝试加载已训练模型
try:
    model.load_weights(path_checkpoint)
except Exception as e:
    print(e)
# 定义early stopping 如果3个epoch内validation loss没有改善则停止训练
early_stopping = EarlyStopping(monitor='val_loss', patience=3, verbose=1)
# 自动降低learning rate
lr_reduction = ReduceLROnPlateau(monitor='val_loss',
                                 factor=0.1, min_lr=1e-5, patience=0,
                                 verbose=1)
# 定义callback函数
callback = [
    early_stopping,
    checkpoint,
    lr_reduction
]
# 开始训练
model.fit(X_train, y_train,
          validation_split=0.1,
          epochs=20,
          batch_size=128,
          callbacks=callback)
# 查看训练结果：
result = model.evaluate(X_test, y_test)
print('Accuracy:{0:.2%}'.format(result)[1])


# 预测我们自己的文本
def predict_sentiment(text):
    text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——！，。？、~@#￥%……&（）]+", "", text)
    # 分词
    cut = jieba.cut(text)
    cut_list = [i for i in cut]
    # tokenize
    for i,word in enumerate(cut_list):
        try:
            cut_list[i] = cn_model.vocab[word].index
        except KeyError:
            cut_list[i]=0
    # padding
    tokens_pad = pad_sequences([cut_list],maxlen=max_tokens,
                               padding='pre',truncating='pre')
    # 预测
    result = model.predict(x=tokens_pad)
    coef = result[0][0]
    if coef>=0.5
        print('是一例正面评价','output=%.2f'%coef)
    else:
        print('是一例负面评价', 'output=%.2f' % coef)
